package hit.core;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import hit.reports.Visitor;

/**
 * Represents a Product in the HIT system
 * */
public class Product extends HITBase {
	private Date creation;
	private Barcode barcode;
	private String description;
	private Size size;
	private int shelfLife;
	private int threeMonth;
	private Set<ProductContainer> containers;
	private List<Integer> containerIDs;
	
	/**
	 * Default constructor
	 * */
	public Product() {
		creation = null;
		barcode = null;
		description = "";
		size = null;
		shelfLife = 0;
		threeMonth = 0;
		containers = new HashSet<ProductContainer>();
		containerIDs = new ArrayList<Integer>();
	}
	
	/**
	 * Checks that a Product is a valid Product. It checks the following
	 * properties:
	 * <ul>
	 * 	<li>Valid Barcode</li>
	 * 	<li>Creation date is in the past</li>
	 *  <li>Valid Size</li>
	 *  <li>Shelf life and three month storage are not negative</li>
	 * </ul>
	 * @param product the Product to check
	 * @return whether the Product is valid
	 * */
	public static boolean IsValid(Product product) {
	
		boolean result = true;
		
		if (product == null)
			result = false;
		
		// Check CreationDate
		if (product.creation == null)
			result = false;
		else
		{
			if (product.creation.after(new Date()))
				result = false;
			
			Calendar test = Calendar.getInstance();
			test.set(2000, 0, 1);
			if (product.creation.before(test.getTime()))
				result = false;
		}
		
		//check that the description is non empty
		if (product.GetDescription().equals(""))
			result = false;
		
		// Check Barcode.
		if (!Barcode.IsValid(product.barcode))
			result = false;
		
		// Check Size
		if (!Size.IsValid(product.size))
			result = false;
		
		// Check ShelfLife
		if (product.shelfLife < 0)
			result = false;
		
		// Check ThreeMonth
		if (product.threeMonth < 0)
			result = false;
		
		// All tests passed.
		return result;
	}
	
	/**
	 * Creates a new {@link Item} that is an instance of this Product.
	 * @param The Product container to put the item into.
	 * @return the new Item
	 * */
	public Item CreateItem(ProductContainer container, Date entryDate) {
		
		Item newItem = new Item();
		
		Barcode barcode = HomeInventoryTracker.Instance().GetNextItemBarcode();
		newItem.SetBarcode(barcode);
		
		newItem.SetEntry(entryDate);
		
		/*
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(entryDate);
		calendar.add(Calendar.MONTH, this.shelfLife);
		newItem.SetExpiration(calendar.getTime()); //*/
		
		newItem.SetProduct(this);
		for (ProductContainer ct : containers){
			if (ct.GetRootContainer().equals(container.GetRootContainer())){
				newItem.SetContainer(ct);
			}
		}
		if (newItem.GetContainer() == null){
			newItem.SetContainer(container);
		}
		
		return newItem;
	}
	
	/**
	 * Add a new {@link ProductContainer} to the Set of ProductContainers that hold this
	 * Product.
	 * @param productContainer the new ProductContainer
	 * */
	public void AddProductContainer(ProductContainer productContainer) {
		
		if (productContainer != null)
		{
			containers.add(productContainer);
		}
	}
	
	/**
	 * Get the ProductContainer ids associated with the Product
	 * @return a List of ids
	 * */
	public List<Integer> GetContainerIDs() {
		if (containers.size() != 0)
		{
			List<Integer> ids = new ArrayList<Integer>();
			
			for (ProductContainer pc : containers) {
				ids.add(pc.getId());
			}
			
			return ids;
		}
		else
			return containerIDs;
	}
	
	/**
	 * Set the List of ProductContainer ids associated with this Product
	 * @param containerIDs the List of ids
	 * */
	public void SetContainerIDs(List<Integer> containerIDs) {
		this.containerIDs = containerIDs;
	}
	
	/**
	 * Remove the specified {@link ProductContainer} from the Set of ProductContainers that
	 * hold this Product.
	 * @param productContainer the ProductContainer to remove
	 * @return the removed ProductContainer
	 * */
	public ProductContainer RemoveProductContainer(ProductContainer productContainer) {
		
		Iterator<ProductContainer> iter = GetProductContainerIterator();
		while (iter.hasNext())
		{
			ProductContainer temp = iter.next();
			
			if (temp == productContainer)
			{
				containers.remove(productContainer);
				return temp;
			}
		}
				
		return null;
	}
	
	/***
	 * Gets an iterator for the list of Product Containers for the Product.
	 * @return The Iterator.
	 */
	public Iterator<ProductContainer> GetProductContainerIterator() {
		return containers.iterator();
	}
	
	/**
	 * @return the creation Date of this Product
	 * */
	public Date GetCreation() {
		return creation;
	}
	
	public Date GetEarliestItemEntry(ItemIndex index) {
		
		Date earliest = this.GetCreation();
		Iterator<Item> iter = index.GetItemsByProduct(this);
		
		while (iter.hasNext())
		{
			Item i = iter.next();
			if (earliest.after(i.GetEntry()))
				earliest = (Date)i.GetEntry().clone();
		}
		
		iter = index.GetRemovedItemIterator();
		
		while (iter.hasNext())
		{
			Item i = iter.next();
			if (i.GetProduct() == this)
			{
				if (earliest.after(i.GetEntry()))
					earliest = (Date)i.GetEntry().clone();
			}
		}
		
		return earliest;
	}
	
	/**
	 * @param creation - the new creation date for the Product
	 * @throws IllegalArgumentException thrown if creation is a Date in the past
	 * */
	public void SetCreation(Date creation) throws IllegalArgumentException {
		if (creation == null || creation.after(new Date()))
			throw new IllegalArgumentException();
		
		this.creation = creation;
	}
	
	/**
	 * @return the Product's Barcode
	 * */
	public Barcode GetBarcode() {
		return barcode;
	}
	
	/**
	 * @param barcode the new {@link Barcode} for the Product
	 * @throws IllegalArgumentException thrown if the Barcode is not valid according to
	 * the {@link Barcode#isValid(Barcode) Barcode.isValid(Barcode)} method.
	 * */
	public void SetBarcode(Barcode barcode) throws IllegalArgumentException {
		if (!Barcode.IsValid(barcode))
			throw new IllegalArgumentException();
		
		this.barcode = barcode;
	}
	
	/**
	 * @return the description of the Product
	 * */
	public String GetDescription() {
		return description;
	}
	
	/**
	 * @param description the new Product description
	 * */
	public void SetDescription(String description) {
		this.description = description;
	}
	
	/**
	 * @return the Size of the Product
	 * */
	public Size GetSize() {
		return size;
	}
	
	/**
	 * @param the new {@link Size} for the Product
	 * @throws IllegalArgumentException thrown if the Size is not valid according to
	 * the {@link Size#isValid(Size) Size.isValid(Size)} method.
	 * */
	public void SetSize(Size size) throws IllegalArgumentException {
		if (!Size.IsValid(size))
			throw new IllegalArgumentException();
		
		this.size = size;
	}

	/**
	 * @return the shelf life of the Product
	 * */
	public int GetShelfLife() {
		return shelfLife;
	}

	/**
	 * @param shelfLife the new shelf life for the Product
	 * @throws IllegalArgumentException thrown if the shelf life is negative
	 * */
	public void SetShelfLife(int shelfLife) throws IllegalArgumentException{
		if (shelfLife < 0)
			throw new IllegalArgumentException();
		
		this.shelfLife = shelfLife;
	}
	
	/**
	 * @return the amount it takes to be a three month supply of the Product
	 * */
	public int GetThreeMonth() {
		return threeMonth;
	}
	
	/**
	 * @param threeMonth the new three month supply amount
	 * @throws IllegalArgumentException thrown if the three month supply amount
	 * is negative
	 * */
	public void SetThreeMonth(int threeMonth) throws IllegalArgumentException{
		if (threeMonth < 0)
			throw new IllegalArgumentException();
		
		this.threeMonth = threeMonth;
	}
	
	public boolean IsContainedBy(ProductContainer container){
		Iterator<ProductContainer> iter = containers.iterator();
		while(iter.hasNext()){
			ProductContainer next = iter.next();
			if (next.equals(container)){
				return true;
			}
		}
		return false;
	}
	
	public boolean IsInStorageUnit(StorageUnit unit) {
		
		Iterator<ProductContainer> iter = containers.iterator();
		
		while (iter.hasNext()) {
			ProductContainer next = iter.next();
			
			if (next.GetRootContainer() == unit)
				return true;
		}
		
		return false;
	}
	
	public void TransferProduct(ProductContainer initial, ProductContainer destination) {
		
		containers.remove(initial);
		containers.add(destination);
	}
	
	public void Accept(Visitor visitor){
		visitor.VisitProduct(this);
	}
}
